/**
 * @name : JsTpl
 * @version : 0.0.3
 * @author: soyokaze
 * @last modify: 2013-11-19 0:39:23
 * @copyright: hsiaosiyuan[at]gmail.com
 * you can do any copy, but please indicate the source
 * @update:
 * 1. check the usage of context, the usage in former version is like
 *
 * var context = {data:1};
 * yourCTpl.render(context);
 *
 * then in your template:
 * data is {{this.data}}
 *
 * current usage is:
 * var context = {data:1};
 * yourCTpl.render(context);
 *
 * then in you template:
 * data os {{context.data}} note: must use context.yourCustomData to retrieve your data
 */
(function () {
    var JsTpl = function (strTpl, debug) {
        this.strTpl = strTpl || '';
        this.cTpl = null;
        this.debug = debug || false;

        this.syntaxReg = /\{\{([^{}]+)\}\}/g;
    };

    JsTpl._syntaxDict = {
        'if(.*):': new RegExp('^if(.*):$'),
        'else if(.*):': new RegExp('^else if(.*):$'),
        'else:': new RegExp('^else:$'),
        'endif;': new RegExp('^endif;$'),
        'for(.*):': new RegExp('^for(.*):$'),
        'endfor;': new RegExp('^endfor;$')
    };

    JsTpl.prototype._getSyntax = function (str) {
        var syntaxDict = JsTpl._syntaxDict;
        var ret = '';

        for (var p in syntaxDict) {
            if (syntaxDict.hasOwnProperty(p)) {
                if (syntaxDict[p].test(str)) {
                    ret = p;
                    break;
                }
            }
        }

        return ret;
    };

    JsTpl.prototype._translateSyntax = function (str) {
        var that = this;

        return str.replace(this.syntaxReg, function (m, p1) {
            var syntax = that._getSyntax(p1.replace(/^\s*|\s*$/g, ''));
            var part1 = '';
            var isDefOutput = false;

            switch (syntax) {
                case 'if(.*):':
                    part1 = p1.replace('):', '){');
                    break;
                case 'else if(.*):':
                    part1 = p1.replace('else', '}else').replace('):', '){');
                    break;
                case 'else:':
                    part1 = p1.replace('else', '}else').replace(':', '{');
                    break;
                case 'endif;':
                    part1 = '}';
                    break;
                case 'for(.*):':
                    part1 = p1.replace('):', '){');
                    break;
                case 'endfor;':
                    part1 = '}';
                    break;
                default :
                    part1 = p1;
                    if (part1.indexOf('=') == -1) {
                        isDefOutput = true;
                    }
                    break;
            }

            if (isDefOutput) {
                return 'output += ' + part1 + ';';
            } else {
                return part1;
            }
        });
    };

    JsTpl.prototype._translateNormal = function (str) {
        return "output += '" + str.replace(/\r{0,1}\n/g, '').replace(/'/g, "\\'") + "';";
    };

    JsTpl.prototype._makeFnContentBody = function (str) {
        if (this.syntaxReg.test(str)) {
            var matches, startIndex = 0, items = [], regex = this.syntaxReg, normal = '', syntax = '', len = str.length;
            regex.lastIndex = 0;

            while ((matches = regex.exec(str)) !== null && startIndex < len) {
                normal = str.slice(startIndex, matches.index);
                syntax = str.slice(matches.index, regex.lastIndex);

                normal = this._translateNormal(normal);

                /**
                 * in the invoking 'this._translateSyntax(syntax)' we use the function String.replace,
                 * it uses the same Regexp 'this.syntaxReg' as we are using here. after calling the String.replace, it
                 * will reset the lastIndex of the Regexp 'this.syntaxReg', so we need to save the lastIndex and reset it
                 * to before calling the String.replace
                 */
                startIndex = regex.lastIndex;
                syntax = this._translateSyntax(syntax);
                regex.lastIndex = startIndex;

                items.push(normal);
                items.push(syntax);
            }

            if (startIndex > 0 && startIndex < len) {
                normal = str.slice(startIndex, len);
                normal = this._translateNormal(normal);
                items.push(normal);
            }

            return items;
        }

        return [this._translateNormal(str)];
    };

    JsTpl.prototype._process = function () {
        var fnContentHeader = 'context = context || {}; var output = \'\';';
        var fnContentBody = this._makeFnContentBody(this.strTpl).join('\n');
        var fnContentFooter = 'return output;';

        var fnContent = fnContentHeader + fnContentBody + fnContentFooter;

        if (this.debug) {
            console.log(fnContent);
        }

        var processed = new Function(['context'], fnContent);
        this.cTpl = new JsTpl.CTpl(processed);
    };

    JsTpl.prototype.getCTpl = function () {
        if (this.cTpl === null) {
            this._process();
        }

        return this.cTpl;
    };

    JsTpl.CTpl = function (processed) {
        this.processed = processed;
    };

    JsTpl.CTpl.prototype.render = function (context) {
        return this.processed(context);
    };

    window.JsTpl = JsTpl;

    if (document.addEventListener && document.querySelector && ('dataset' in document.createElement('script'))) {
        document.addEventListener('DOMContentLoaded', function () {
            JsTpl.renderer = {};
            var scripts = document.querySelectorAll('script[type="text/jsTpl"]'), script, method;
            for (var i = 0, len = scripts.length; i < len; i++) {
                script = scripts[i];
                method = script.dataset.method;

                if (method) {
                    JsTpl.renderer[method] = new JsTpl(script.innerHTML).getCTpl();
                }
            }
        });
    }
})();